/**
 * 
 */
package com.gr.staffpm.pages.schedule;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.apache.wicket.ResourceReference;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebComponent;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.parser.XmlTag;
import org.joda.time.DateTime;

import com.gr.staffpm.css.CssScope;
import com.gr.staffpm.js.StaffPMJSScope;
import com.gr.staffpm.pages.schedule.model.Calendar;
import com.gr.staffpm.pages.schedule.model.Event;


/**
 * @author Graham Rhodes 5 Apr 2011 19:55:35
 */
public class CalendarPanel extends Panel implements IHeaderContributor {

    private static final long serialVersionUID = 1L;
    private final Map<String, Object> options = new HashMap<String, Object>();

    public CalendarPanel(String id) {
        super(id);

        WebMarkupContainer panel = new WebMarkupContainer("panel");

        options.put("calendars", new ArrayList<Calendar>());
        options.put("evts", new ArrayList<Event>());
        DateTime startDate = new DateTime().withDayOfMonth(new DateTime().dayOfMonth().getMinimumValue());
        if (startDate.getDayOfWeek() != 7)
            startDate = startDate.minusDays(startDate.getDayOfWeek());
        options.put("viewStart", startDate);
        DateTime endDate = new DateTime().withDayOfMonth(new DateTime().dayOfMonth().getMaximumValue());
        if (endDate.getDayOfMonth() != 6) {
            endDate = endDate.plusDays(6 - (endDate.getDayOfWeek() == 7 ? 0 : endDate.getDayOfWeek()));
        }
        options.put("viewEnd", endDate);

        // This custom component fills the <script> tag with the script returned by getCalScript().
        panel.add(new WebComponent("calScript") {

            private static final long serialVersionUID = 1L;

            @Override
            protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) {
                replaceComponentTagBody(markupStream, openTag, getCalScript());
            }

            @Override
            protected void onComponentTag(ComponentTag tag) {
                super.onComponentTag(tag);
                // always transform the tag to <span></span> so even labels defined as <span/> render
                tag.setType(XmlTag.OPEN);
            }
        });

        add(panel);
    }

    @Override
    public void renderHead(IHeaderResponse response) {
        response.renderCSSReference(new ResourceReference(CssScope.class, "extjs/ext-all.css"));
        response.renderCSSReference(new ResourceReference(CssScope.class, "extjs/calendar.css"));
        response.renderCSSReference(new ResourceReference(CssScope.class, "extjs/extensible-all.css"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/ext-base-debug.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/ext-all-debug.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/extensible-all-debug.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/MemoryEventStore.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/CalendarStore.js"));
        /*response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/Ext.calendar.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DayHeaderTemplate.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DayBodyTemplate.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DayViewTemplate.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/BoxLayoutTemplate.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/MonthViewTemplate.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/CalendarScrollManager.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/StatusProxy.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/CalendarDD.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DayViewDD.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/EventRecord.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/MonthDayDetailView.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/CalendarPicker.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/WeekEventRenderer.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/CalendarView.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/MonthView.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DayHeaderView.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DayBodyView.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DayView.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/WeekView.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/DateRangeField.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/ReminderField.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/EventEditForm.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/EventEditWindow.js"));
        response.renderJavascriptReference(new ResourceReference(StaffPMJSScope.class, "extjs/CalendarPanel.js"));*/
    }

    /**
     * Returns Javascript code which renders the calendar.
     * @throws RuntimeException if CalPanel.js cannot be loaded.
     */
    @SuppressWarnings("unchecked")
    private String getCalScript() {
        try {
            final String str = getResourceContents(CalendarPanel.class, "CalPanel.js");

            String calList = calendarsToString((ArrayList<Calendar>) options.get("calendars"));
            String evtList = eventsToString((ArrayList<Event>) options.get("evts"));

            return String.format(str, calList, evtList, viewStartToString(), viewEndToString());
        } catch (IOException e) {
            throw new RuntimeException("Unable to load CalPanel.js", e);
        }
    }

    private String viewEndToString() {
        return "new Date(" + ((DateTime) options.get("viewEnd")).getMillis() + ");";
    }

    private String viewStartToString() {
        return "new Date(" + ((DateTime) options.get("viewStart")).getMillis() + ");";
    }

    private String calendarsToString(ArrayList<Calendar> list) {
        if (!list.isEmpty()) {
            final StringBuilder str = new StringBuilder();
            str.append("{\"calendars\":[");
            for (Calendar cal : list) {
                str.append("{");
                str.append("\"id\":" + cal.getId() + ",");
                str.append("\"title\":\"" + cal.getTitle() + "\",");
                str.append("\"color\":" + cal.getColour());
                str.append("},");
            }
            str.deleteCharAt(str.lastIndexOf(","));
            str.append("]};");
            return str.toString();
        } else
            return "null";
    }

    private String eventsToString(ArrayList<Event> list) {
        if (!list.isEmpty()) {
            final StringBuilder str = new StringBuilder();
            str.append("{\"evts\":[");
            for (Event evt : list) {
                str.append("{");
                str.append("\"id\":" + evt.getId() + ",");
                str.append("\"cid\":" + evt.getCid() + ",");
                str.append("\"title\":\"" + evt.getTitle() + "\",");
                str.append("\"start\": new Date(" + evt.getStart().getMillis() + "),");
                str.append("\"end\": new Date(" + evt.getEnd().getMillis() + "),");
                str.append("\"ad\":" + evt.isAd());
                str.append("},");
            }
            str.deleteCharAt(str.lastIndexOf(","));
            str.append("]};");
            return str.toString();
        } else
            return "null";
    }

    @SuppressWarnings("unchecked")
    public void addCalendar(Calendar cal) {
        ((ArrayList<Calendar>) options.get("calendars")).add(cal);
    }

    @SuppressWarnings("unchecked")
    public void addEvent(Event evt) {
        ((ArrayList<Event>) options.get("evts")).add(evt);
    }

    @SuppressWarnings("unchecked")
    public void clearEvents() {
        ((ArrayList<Event>) options.get("evts")).clear();
    }

    /**
     * Returns the contents of the given resource.
     * @param location The resource path.
     * @return The resource's content.
     * @throws FileNotFoundException If the resource cannot be found.
     * @throws IOException If anything goes wrong while reading the resource.
     */
    private static String getResourceContents(Class<?> clazz, String location) throws IOException {
        InputStream in = clazz.getResourceAsStream(location);
        if (in == null)
            throw new FileNotFoundException("'" + location + "' not found.");
        return getStreamContents(new InputStreamReader(in));
    }

    /**
     * Returns the complete content of the given stream.
     * @throws IOException If anything goes wrong while reading the stream.
     */
    private static String getStreamContents(InputStreamReader inputStreamReader) throws IOException {
        StringBuffer str = new StringBuffer();
        BufferedReader in = new BufferedReader(inputStreamReader);

        while (true) {
            String line = in.readLine();
            if (line == null)
                break;
            str.append(line);
            str.append("\n");
        }

        return str.toString();
    }

    public void setView(DateTime start, DateTime end) {
        options.put("viewStart", start);
        options.put("viewEnd", end);
    }

}
